#include "value.hpp"

namespace BMD
{
  /*************************************
   * Value printing
   ************************************/
  void output_visitor::operator() (const NullValue& nv) const
  {
    out << "uninitialized value";
  }
  void output_visitor::operator() (int i) const
  {
    out << i;
  }
  void output_visitor::operator() (double d) const
  {
    out << d;
  }
  void output_visitor::operator() (string s) const
  {
    if (print_full) out << "\"";
    out << s;
    if (print_full) out << "\"";
  }

  /** Pretty-prints a list. Prints the first three elements
      of the list, separated by spaces, using ... if necessary
      to indicate a larger-sized list. */
  void output_visitor::operator() (ListRef l) const
  {
    
    out << "(";
    ListRef head = l;
    int i = 0;
    const int MAX_VALUES = 3;

    // if we're not printing the full value, we check to see that we only
    // printed 3 values;
    while (print_full || i < MAX_VALUES) 
    {      
      if (!head.get())
      {
        out << ")";
        return;
      }
      if (i != 0)
        out << " ";
      out << head->get_car();
      head = head->get_cdr();

      ++i;
    }
    if(head.get())
      out << " ...";
    out << ")";
  }

  /** Pretty-prints an block, based on its tag.  */
  void output_visitor::operator() (BlockRef br) const
  {    
    switch (br->get_tag())
    {
    case Block::Array:
      print_as_tuple(br, '[', ']');
      break;
    case Block::Struct:
      print_as_tuple(br, '{', '}');
      break;
    case Block::Reference:
      print_reference(br);
      break;
    default:
      break;
    }
  }

  /** Prints the first three elements
      of the array, separated by commas, using ... if necessary
      to indicate a larger-sized array. */
  void output_visitor::print_as_tuple(BlockRef a, char start_char, char end_char) const
  { 
    const int MAX_VALUES = 3;

    out << start_char;
    int size = a->get_size();
    if (size == 0)
    {
      out << end_char;
      return;
    }
    
    const int num_to_print = 
      print_full ? size : (size < MAX_VALUES ? size : MAX_VALUES);
    
    // print up to 3, not going over the available number
    out << a->get_elem(0);
    for (int i = 1; i < num_to_print; ++i)
      out << ", " << a->get_elem(i);
    
    if (size > num_to_print)
      out << ", ...";
    out << end_char;
  }

  void output_visitor::print_reference(BlockRef a) const
  {
    out << "ref(" << a->get_elem(0) << ")";
  }

  
  /*************************************
   * Type identification
   ************************************/
  string typename_visitor::operator() (BlockRef br) const
  {
    switch (br->get_tag())
    {
    case Block::Array:
      return "array";
    case Block::Struct:
      return "struct";
    case Block::Reference:
      return "reference";
    default:
      throw type_error("Unknown tag for block");
    }
  }
  
  /*************************************
   * Cloning
   ************************************/
  Value clone_visitor::operator() (NullValue nv) const
  {
    return Value();
  }

  Value clone_visitor::operator() (int i) const
  {
    return Value(i);
  }

  Value clone_visitor::operator() (double d) const
  {
    return Value(d);
  }

  Value clone_visitor::operator() (string s) const
  {
    return Value(s);
  }

  Value clone_visitor::operator() (ListRef list) const
  {
    if (!list.get())
      return Value(ListRef());
    
    Value car = list->get_car();
    Value car_copy = deep ? boost::apply_visitor(clone_visitor(deep), car) :
      list->get_car();

    ListRef cdr_copy = clone_visitor(deep)(list->get_cdr()).get_list();
    
    return Value(ListRef(new List(car_copy, cdr_copy)));
  }
    
  Value clone_visitor::operator() (BlockRef array) const
  {
    Block* copy = new Block(array->get_size());

    // clone each element of the original array
    for (unsigned int i = 0; i < array->get_size(); ++i)
    {
      Value elem_i = array->get_elem(i);
      if (deep)
        copy->set_elem(i, boost::apply_visitor(clone_visitor(deep), elem_i));
      else
        copy->set_elem(i, elem_i);
    }

    return Value(BlockRef(copy));
  }

}
